#!/bin/sh
#
# $FreeBSD$
#

# PROVIDE: ix-proftpd
# REQUIRE: FILESYSTEMS ix-ssl
# BEFORE: proftpd

. /etc/rc.freenas

# Defaults
proftpd_enable=${proftpd_enable:-"NO"}
proftpd_flags=${proftpd_flags:-""}
proftpd_config=${proftpd_config:-"/usr/local/etc/proftpd.conf"}
proftpd_delaytable=${proftpd_delaytable:-"/var/run/proftpd/proftpd.delay"}
proftpd_displaylogin=${proftpd_displaylogin:-"/var/run/proftpd/proftpd.motd"}
proftpd_tcpaccessfilesallow=${proftpd_tcpaccessfilesallow:-"/etc/hosts.allow"}
proftpd_tcpaccessfilesdeny=${proftpd_tcpaccessfilesdeny:-"/etc/hosts.allow"}
proftpd_umaskfile=${proftpd_umaskfile:-"077"}
proftpd_umaskdirectory=${proftpd_umaskdirectory:-"022"}
proftpd_maxinstances=${proftpd_maxinstances:-"none"}
proftpd_defaulttransfermode=${proftpd_defaulttransfermode:-"ascii"}
proftpd_deferwelcome=${proftpd_deferwelcome:-"off"}
proftpd_anonymousrootdir=${proftpd_anonymousrootdir:-"/mnt"}
proftpd_multilinerfc2228=${proftpd_multilinerfc2228:-"off"}
proftpd_timeoutlogin=${proftpd_timeoutlogin:-"300"}
proftpd_timeoutnotransfer=${proftpd_timeoutnotransfer:-"300"}
proftpd_timeoutstalled=${proftpd_timeoutstalled:-"3600"}
proftpd_defaultrootdir=${proftpd_defaultrootdir:-"~"}
proftpd_defaultrootgroupexpr=${proftpd_defaultrootgroupexpr:-"!wheel"}
proftpd_requirevalidshell=${proftpd_requirevalidshell:-"off"}
proftpd_deleteabortedstores=${proftpd_deleteabortedstores:-"off"}
proftpd_tlsverifyclient=${proftpd_tlsverifyclient:-"off"}
proftpd_tlsoptions=${proftpd_tlsoptions:-"NoCertRequest"}
proftpd_tlsprotocol=${proftpd_tlsprotocol:-"SSLv3 TLSv1.2"}
proftpd_tlsrequired=${proftpd_tlsrequired:-"on"}
proftpd_tcpaccesssysloglevels=${proftpd_tcpaccesssysloglevels:-"info warn"}
proftpd_tcpservicename=${proftpd_tcpservicename:-"ftpd"}
proftpd_timesgmt=${proftpd_timesgmt:-"off"}
proftpd_moddelay_enable=${proftpd_moddelay_enable:-"YES"}
proftpd_modwrap_enable=${proftpd_modwrap_enable:-"YES"}
proftpd_modctrls_controlsengine=${proftpd_modctrls_controlsengine:-"off"}
proftpd_modctrls_controlsacls=${proftpd_modctrls_controlsacls:-"all allow group wheel"}
proftpd_modctrls_controlsauthfreshness=${proftpd_modctrls_controlsauthfreshness:-"10"}
proftpd_modctrls_controlsmaxclients=${proftpd_modctrls_controlsmaxclients:-"5"}
proftpd_modctrls_controlslog=${proftpd_modctrls_controlslog:-"/var/log/proftpd/controls.log"}
proftpd_modctrls_controlsinterval=${proftpd_modctrls_controlsinterval:-"10"}
proftpd_modban_bancontrolsacls=${proftpd_modban_bancontrolsacls:-"all allow group wheel"}
proftpd_modban_banlog=${proftpd_modban_banlog:-"/var/log/proftpd/ban.log"}
proftpd_modban_banmessage=${proftpd_modban_banmessage:-"Host %a has been banned"}
proftpd_modban_bantable=${proftpd_modban_bantable:-"/var/run/proftpd/ban.tab"}

# If $1 is set, then return $2
ifset()
{
    if [ -n "$1" ]; then
	echo -n $2
    fi
}

# If $1 is not 0, then return $2
ifnz()
{
    if [ "$1" -ne 0 ]; then
	echo -n $2
    fi
}

# If $1 == 1, return $2, otherwise return nothing
bool_on()
{
    if [ "$1" -gt 0 ]; then
	echo -n $2
    else
	echo -n "# $2"
    fi
}

# Return "on" for '1' and "off" for '0'
on_off()
{
    case $1 in
	0) echo "off";;
	1) echo "on";;
    esac
}

generate_proftp()
{
    RO_FREENAS_CONFIG=$(ro_sqlite ${name} 2> /tmp/${name}.fail && rm /tmp/${name}.fail)
    trap 'rm -f ${RO_FREENAS_CONFIG}' EXIT

    local IFS="|"
#XXX missing: ftp_chrooteveryone ftp_tlsrequired ftp_mod_ban
    local ftp_chrooteveryone=0 ftp_mod_ban=0 ftp_banner
#XXX unused: ftp_chrooteveryone
    local f="ftp_port ftp_clients ftp_ipconnections ftp_loginattempt ftp_timeout ftp_rootlogin ftp_onlyanonymous ftp_onlylocal ftp_filemask ftp_dirmask ftp_fxp ftp_resume ftp_defaultroot ftp_ident ftp_reversedns ftp_masqaddress ftp_passiveportsmin ftp_passiveportsmax ftp_localuserbw ftp_localuserdlbw ftp_anonuserbw ftp_anonuserdlbw ftp_tls ftp_tls_policy ftp_tls_opt_allow_client_renegotiations ftp_tls_opt_allow_dot_login ftp_tls_opt_allow_per_user ftp_tls_opt_common_name_required ftp_tls_opt_enable_diags ftp_tls_opt_export_cert_data ftp_tls_opt_no_cert_request ftp_tls_opt_no_empty_fragments ftp_tls_opt_no_session_reuse_required ftp_tls_opt_stdenvvars ftp_tls_opt_dns_name_required ftp_tls_opt_ip_address_required ftp_anonpath"
    eval local $f
    local sf=$(var_to_sf $f)
    ${FREENAS_SQLITE_CMD} ${RO_FREENAS_CONFIG} "
        SELECT
            $sf,
            sc.cert_name
        FROM
            services_ftp
        LEFT OUTER JOIN
            system_certificate as sc
        ON  
            ftp_ssltls_certificate_id = sc.id
        ORDER BY
            -services_ftp.id
        LIMIT
            1
    " | \
    while eval read $f certname; do
	# Check if required directories exists.
	[ ! -d "/var/run/proftpd" ] && mkdir "/var/run/proftpd"
	[ ! -d "/var/log/proftpd" ] && mkdir "/var/log/proftpd"

	# Check if required files exists.
	[ ! -e "${proftpd_delaytable}" ] && cat /dev/null > "${proftpd_delaytable}"
	[ ! -e "${proftpd_tcpaccessfilesallow}" ] && cat /dev/null > "${proftpd_tcpaccessfilesallow}"
	[ ! -e "${proftpd_tcpaccessfilesdeny}" ] && cat /dev/null > "${proftpd_tcpaccessfilesdeny}"
	[ ! -e "/var/log/wtmp" ] && touch /var/log/wtmp && chmod 644 /var/log/wtmp

        ftp_banner=$(${FREENAS_SQLITE_CMD} ${RO_FREENAS_CONFIG} "SELECT ftp_banner FROM services_ftp ORDER BY -services_ftp.id LIMIT 1")

	# Create welcome message file
	if [ -z "${ftp_banner}" ]; then
		_productname="FreeNAS"
		echo "Welcome to ${_productname} FTP Server" > ${proftpd_displaylogin}
	else
		echo ${ftp_banner} > ${proftpd_displaylogin}
	fi

	cat <<EOF > ${proftpd_config}
ServerName "$(hostname) FTP Server"
ServerType standalone
DefaultServer on
DefaultAddress localhost
UseIPv6 on
Port ${ftp_port}
User nobody
Group nogroup
Umask ${ftp_filemask} ${ftp_dirmask}
SyslogFacility ftp
MultilineRFC2228 ${proftpd_multilinerfc2228}
DisplayLogin ${proftpd_displaylogin}
DeferWelcome ${proftpd_deferwelcome}
TimeoutIdle ${ftp_timeout}
TimeoutLogin ${proftpd_timeoutlogin}
TimeoutNoTransfer ${proftpd_timeoutnotransfer}
TimeoutStalled ${proftpd_timeoutstalled}
MaxInstances ${proftpd_maxinstances}
$(ifnz ${ftp_clients} "MaxClients ${ftp_clients}")
$(ifnz ${ftp_ipconnections} "MaxConnectionsPerHost ${ftp_ipconnections}")
$(ifnz ${ftp_loginattempt} "MaxLoginAttempts ${ftp_loginattempt}")
DefaultTransferMode ${proftpd_defaulttransfermode}
AllowForeignAddress $(on_off ${ftp_fxp})
$(ifset ${ftp_masqaddress} "MasqueradeAddress ${ftp_masqaddress}")
IdentLookups $(on_off ${ftp_ident})
UseReverseDNS $(on_off ${ftp_reversedns})
$(ifnz ${ftp_passiveportsmin} "PassivePorts ${ftp_passiveportsmin} ${ftp_passiveportsmax}")

EOF

	if [ ${ftp_onlyanonymous} -eq 1 -a -d "${ftp_anonpath}" ]; then
	    cat <<EOF >> ${proftpd_config}
<Anonymous ${ftp_anonpath}>
  User ftp
  Group ftp
  UserAlias anonymous ftp
  $(ifnz ${ftp_anonuserbw} "TransferRate STOR ${ftp_anonuserbw}")
  $(ifnz ${ftp_anonuserdlbw} "TransferRate RETR ${ftp_anonuserdlbw}")
  <Limit LOGIN>
    AllowAll
  </Limit>
</Anonymous>

EOF
        fi

	if [ ${ftp_onlylocal} -eq 1 ]; then
		cat <<EOF >> ${proftpd_config}
<Limit LOGIN>
  AllowAll
</Limit>

EOF
        fi
        if [ ${ftp_onlyanonymous} -ne 1 ] && [ ${ftp_onlylocal} -ne 1 ]; then
	        cat <<EOF >> ${proftpd_config}
<Limit LOGIN>
  AllowGroup ftp
$(bool_on ${ftp_rootlogin} "  AllowGroup wheel")
  DenyAll
</Limit>

EOF
        fi

	cat <<EOF >> ${proftpd_config}
<Global>
  RequireValidShell ${proftpd_requirevalidshell}
$(bool_on ${ftp_defaultroot} "  DefaultRoot ~ ${proftpd_defaultrootgroupexpr}")
$(bool_on ${ftp_rootlogin} "  RootLogin on")
  AllowOverwrite on
$(bool_on ${ftp_resume} "  AllowRetrieveRestart on")
$(bool_on ${ftp_resume} "  AllowStoreRestart on")
  DeleteAbortedStores ${proftpd_deleteabortedstores}
  $(ifnz ${ftp_localuserbw} "TransferRate STOR ${ftp_localuserbw}")
  $(ifnz ${ftp_localuserdlbw} "TransferRate RETR ${ftp_localuserdlbw}")
  TimesGMT ${proftpd_timesgmt}
</Global>
EOF

	if [ ${ftp_tls} -gt 0 ]; then
# This is hacky, this should be converted to use the ORM.
# system.Certificate contains the raw certificates. The path
# is also only available via the model, so we have to hard code
# it here. The certificates are written to the filesystem using
# the certificate name, so we also have to add the .crt and .key
# extension, when that would be available from the Model via a function.
tls_cert_dir="${SSLDIR}"
tls_options="\
$(ifnz ${ftp_tls_opt_allow_client_renegotiations} "AllowClientRenegotiations") \
$(ifnz ${ftp_tls_opt_allow_dot_login} "AllowDotLogin") \
$(ifnz ${ftp_tls_opt_allow_per_user} "AllowPerUser") \
$(ifnz ${ftp_tls_opt_common_name_required} "CommonNameRequired") \
$(ifnz ${ftp_tls_opt_enable_diags} "EnableDiags") \
$(ifnz ${ftp_tls_opt_export_cert_data} "ExportCertData") \
$(ifnz ${ftp_tls_opt_no_cert_request} "NoCertRequest") \
$(ifnz ${ftp_tls_opt_no_empty_fragments} "NoEmptyFragments") \
$(ifnz ${ftp_tls_opt_no_session_reuse_required} "NoSessionReuseRequired") \
$(ifnz ${ftp_tls_opt_stdenvvars} "StdEnvVars") \
$(ifnz ${ftp_tls_opt_dns_name_required} "dNSNameRequired") \
$(ifnz ${ftp_tls_opt_ip_address_required} "iPAddressRequired")"
if [ ! -z "$(echo ${tls_options}|sed -E 's|( +)||g')" ]; then
	tls_options="TLSOptions $(echo ${tls_options}|sed -E 's|( +)| |g')"
else
	tls_options=""
fi

	    cat <<EOF >> ${proftpd_config}
LoadModule mod_tls.c
<IfModule mod_tls.c>
  TLSEngine on
  TLSProtocol ${proftpd_tlsprotocol}
  ${tls_options}
  TLSRSACertificateFile "${tls_cert_dir}/${certname}.crt"
  TLSRSACertificateKeyFile "${tls_cert_dir}/${certname}.key"
  TLSVerifyClient ${proftpd_tlsverifyclient}
  TLSRequired ${ftp_tls_policy}
</IfModule>
EOF
	fi
	cat <<EOF >> ${proftpd_config}
<IfModule mod_ban.c>
  BanEngine $(on_off ${ftp_mod_ban})
  BanControlsACLs ${proftpd_modban_bancontrolsacls}
  BanLog ${proftpd_modban_banlog}
  BanMessage ${proftpd_modban_banmessage}
# -m "mod_ban/rule"
# -v "concat('  BanOnEvent ',event,' ',occurrence,'/',timeinterval,' ',expire)" -n
# -b
  BanTable ${proftpd_modban_bantable}
</IfModule>

EOF
        ${FREENAS_SQLITE_CMD} ${RO_FREENAS_CONFIG} "SELECT ftp_options FROM services_ftp ORDER BY -id LIMIT 1" >> ${proftpd_config}

	if checkyesno proftpd_moddelay_enable; then
	    cat <<EOF >> ${proftpd_config}
<IfModule mod_delay.c>
  DelayEngine on
  DelayTable "${proftpd_delaytable}"
</IfModule>

EOF
	fi
	# Add mod_wrap
	if checkyesno proftpd_modwrap_enable; then
	    cat <<EOF >> ${proftpd_config}
<IfModule mod_wrap.c>
  TCPAccessFiles ${proftpd_tcpaccessfilesallow} ${proftpd_tcpaccessfilesdeny}
  TCPAccessSyslogLevels ${proftpd_tcpaccesssysloglevels}
  TCPServiceName ${proftpd_tcpservicename}
</ifModule>

EOF
	fi
	# Add mod_ctrls.
	if [ "${proftpd_modctrls_controlsengine}" = "on" ]; then
	    cat <<EOF >> ${proftpd_config}
<IfModule mod_ctrls.c>
  ControlsEngine ${proftpd_modctrls_controlsengine}
  ControlsACLs ${proftpd_modctrls_controlsacls}
  ControlsAuthFreshness
  ControlsMaxClients ${proftpd_modctrls_controlsmaxclients}
  ControlsLog ${proftpd_modctrls_controlslog}
  ControlsInterval ${proftpd_modctrls_controlsinterval}
</IfModule>

EOF
	fi
    done
}

name="ix-proftpd"
start_cmd='generate_proftp'
stop_cmd=':'

load_rc_config $name
run_rc_command "$1"
